#ifndef TESTS_CPP_
#define TESTS_CPP_

#define LOGGING_INFO
#include "logger.h"

#include <gtest/gtest.h>

using std::thread;
using std::string;
using std::make_pair;
using std::bind;



TEST(AllocFunctionTests, ma_minimum_cycle) {
	EXPECT_EQ(1, ma_init(10, 100, "some_path"));
	ma_deinit();
}

TEST(AllocFunctionTests, ma_repeat_init) {
	EXPECT_EQ(1, ma_init(10, 100, "some_path"));
	EXPECT_EQ(0, ma_init(10, 100, "some_path"));
	ma_deinit();
}

TEST(AllocFunctionTests, ma_alloc_basic) {
	EXPECT_EQ(1, ma_init(10, 100, "some_path"));
	size_t id  = ma_alloc(5);
	EXPECT_LE(1, id);
	ma_deinit();
}

TEST(AllocFunctionTests, ma_alloc_free_basic) {
	EXPECT_EQ(1, ma_init(10, 100, "some_path"));
	size_t id  = ma_alloc(5);
	EXPECT_LE(1, id);
	int ret = ma_free(id);
	EXPECT_EQ(1, ret);
	ma_deinit();
}

TEST(AllocFunctionTests, ma_alloc_err_nomem_basic) {
	EXPECT_EQ(1, ma_init(10, 100, "some_path"));
	size_t id = 1;
	for(int i = 0; i < 111; i++) { 
		id = ma_alloc(1);
	}
	EXPECT_EQ(0, id);
	ma_deinit();
}

TEST(AllocFunctionTests, ma_alloc_avoid_nomem) {
	EXPECT_EQ(1, ma_init(100, 1000, "some_path"));

	for(int i = 0; i < 10; i++) { 
		size_t id = ma_alloc(20);
		LOG_INFO("New id : <%lu> <%p>", id, (void*)id);
		EXPECT_LE(1, id);
		int ret = ma_free(id);
		EXPECT_EQ(1, ret);
	}
	
	ma_deinit();
}

TEST(AllocFunctionTests, ma_alloc_err2big) {
	EXPECT_EQ(1, ma_init(100, 1000, "some_path"));

	size_t id = ma_alloc(101);
	EXPECT_EQ(0, id);
	
	ma_deinit();
}

TEST(AllocFunctionTests, ma_free_err_invalid_id) {
	EXPECT_EQ(1, ma_init(10, 100, "some_path"));

	int ret = ma_free(124230);
	EXPECT_EQ(0, ret);
	
	ma_deinit();
}

TEST(AllocFunctionTests, ma_free_err_already_free) {
	EXPECT_EQ(1, ma_init(10, 100, "some_path"));
	size_t id  = ma_alloc(5);
	EXPECT_LE(1, id);
	int ret = ma_free(id);
	EXPECT_EQ(1, ret);
	ret = ma_free(id);
	EXPECT_EQ(0, ret);
	ma_deinit();
}

TEST(AllocFunctionTests, ma_get_basic) {
	EXPECT_EQ(1, ma_init(100, 1000, "some_path"));
	size_t id  = ma_alloc(50);
	EXPECT_LE(1, id);
	void *res = ma_get(id);
	EXPECT_NE(NULL, reinterpret_cast<size_t>(res));
	ma_deinit();
}

TEST(AllocFunctionTests, ma_get_release_basic) {
	EXPECT_EQ(1, ma_init(10, 100, "some_path"));
	size_t id  = ma_alloc(5);
	EXPECT_LE(1, id);
	void *res = ma_get(id);
	EXPECT_NE((void*)NULL, res);
	int ret = ma_release(id);
	EXPECT_EQ(1, ret);
	ma_deinit();
}

TEST(AllocFunctionTests, ma_get_release_multi) {
	EXPECT_EQ(1, ma_init(10, 100, "some_path"));
	size_t id  = ma_alloc(5);
	EXPECT_LE(1, id);
	for(int i = 0; i < 10; i++) {
		void *res = ma_get(id);
		EXPECT_NE((void*)NULL, res);
		int ret = ma_release(id);
		EXPECT_EQ(1, ret);
	}
	ma_deinit();
}

TEST(AllocFunctionTests, ma_get_release_multi_clever) {
	EXPECT_EQ(1, ma_init(10, 100, "some_path"));
	size_t id  = ma_alloc(8);
	void *res = ma_get(id);
	EXPECT_NE((void*)NULL, res);
	*(int *)res = -1;
	int ret = ma_release(id);
	EXPECT_EQ(1, ret);
	
	EXPECT_LE(1, id);
	for(int i = 0; i < 10; i++) {
		void *res = ma_get(id);
		EXPECT_NE((void*)NULL, res);
		EXPECT_EQ((int)(i - 1), *(int *)res);
		*(int *)res = i;
		int ret = ma_release(id);
		EXPECT_EQ(1, ret);
	}
	ma_deinit();
}


TEST(AllocFunctionTests, ma_get_err_invalid_mem) {
	EXPECT_EQ(1, ma_init(10, 100, "some_path"));
	void* res = ma_get(12423);
	EXPECT_EQ(NULL, res);
	ma_deinit();
}

TEST(AllocFunctionTests, ma_get_err_already_free) {
	EXPECT_EQ(1, ma_init(10, 100, "some_path"));
	size_t id  = ma_alloc(5);
	EXPECT_LE(1, id);
	int ret = ma_free(id);
	EXPECT_EQ(1, ret);
	void* res = ma_get(id);
	EXPECT_EQ(NULL, res);
	ma_deinit();
}

TEST(AllocFunctionTests, ma_release_err_invalid_id) {
	EXPECT_EQ(1, ma_init(10, 100, "some_path"));
	int ret = ma_release(12423523);
	EXPECT_EQ(0, ret);
	ma_deinit();
}

TEST(AllocFunctionTests, ma_release_err_already_free) {
	EXPECT_EQ(1, ma_init(10, 100, "some_path"));
	size_t id  = ma_alloc(5);
	EXPECT_LE(1, id);
	int ret = ma_free(id);
	EXPECT_EQ(1, ret);
	ret = ma_release(id);
	EXPECT_EQ(0, ret);
	ma_deinit();
}

TEST(AllocFunctionTests, ma_release_err_already_release_multi) {
	EXPECT_EQ(1, ma_init(10, 100, "some_path"));
	size_t id  = ma_alloc(5);
	EXPECT_LE(1, id);
	for(int i = 0; i < 10; i++) {
		void *res = ma_get(id);
		EXPECT_NE((void*)NULL, res);
		int ret = ma_release(id);
		EXPECT_EQ(1, ret);
		ret = ma_release(id);
		EXPECT_EQ(0, ret);
	}
	ma_deinit();
}

TEST(AllocFunctionTests, ma_allocator_full_cycle) {
	EXPECT_EQ(1, ma_init(45, 100, "some_path"));
	size_t id[10];
	for(int i = 0; i < 10; i++) {
		id[i] = ma_alloc(i);
		EXPECT_LE(1, id[i]);
	}
	for(int i = 0; i < 10; i++) {
		for(int i = 0; i < 10; i+=2) {
			void *res = ma_get(id[i]);
			EXPECT_NE((void *)NULL, res);
		}
		for(int i = 1; i < 10; i+=2) {
			void *res = ma_get(id[i]);
			EXPECT_NE((void *)NULL, res);
		}
		for(int i = 0; i < 10; i+=2) {
			int ret = ma_release(id[i]);
			EXPECT_EQ(1, ret);
		}
		for(int i = 1; i < 10; i+=2) {
			int ret = ma_release(id[i]);
			EXPECT_EQ(1, ret);
		}
	}
	for(int i = 0; i < 10; i++) {
		int ret = ma_free(id[i]);
		EXPECT_LE(1, ret);
	}
	
	ma_deinit();
}

TEST(AllocFunctionTests, ma_allocator_full_cycle_intersect) {
	EXPECT_EQ(1, ma_init(45, 10000, "some_path1"));
	size_t id[10];
	for(int i = 0; i < 10; i++) {
		id[i] = ma_alloc(i);
		EXPECT_LE(1, id[i]);
	}
	for(int i = 0; i < 10; i++) {
		for(int i = 2; i < 10; i+=3) {
			void *res = ma_get(id[i]);
			EXPECT_NE((void *)NULL, res);
		}
		for(int i = 0; i < 10; i+=3) {
			void *res = ma_get(id[i]);
			EXPECT_NE((void *)NULL, res);
		}
		for(int i = 1; i < 10; i+=3) {
			void *res = ma_get(id[i]);
			EXPECT_NE((void *)NULL, res);
		}
		for(int i = 2; i < 10; i+=3) {
			int ret = ma_release(id[i]);
			EXPECT_EQ(1, ret);
		}
		for(int i = 1; i < 10; i+=3) {
			int ret = ma_release(id[i]);
			EXPECT_EQ(1, ret);
		}
		for(int i = 1; i < 10; i+=3) {
			void *res = ma_get(id[i]);
			EXPECT_NE((void *)NULL, res);
		}
		for(int i = 1; i < 10; i+=3) {
			int ret = ma_release(id[i]);
			EXPECT_EQ(1, ret);
		}
		for(int i = 0; i < 10; i+=3) {
			int ret = ma_release(id[i]);
			EXPECT_EQ(1, ret);
		}
	}
	for(int i = 0; i < 10; i++) {
		int ret = ma_free(id[i]);
		EXPECT_LE(1, ret);
	}
	
	ma_deinit();
}




void ma_allocator_multithread_alloc_free_routine()
{
	for (int i = 0; i < 50000; i++) {
		size_t id = ma_alloc(40);
		ASSERT_NE(0, id);
		ASSERT_NE(0, ma_free(id));
	}
}

TEST(AllocFunctionTests, ma_allocator_multithread_alloc_free) {
	int max_threads = 1;
	EXPECT_EQ(1, ma_init(4500, 10000, "some_path"));
	for (int thrs = 1; thrs <= max_threads; thrs++) {
		LOG_INFO("thrs <%d>", thrs);
		
		thread* threads[thrs];
		for (int i = 0; i < thrs; i++)
			threads[i] = new thread(ma_allocator_multithread_alloc_free_routine);
			
		for (int i = 0; i < thrs; i++)
			threads[i]->join();
	}
	ma_deinit();
}


void ma_allocator_simple_multithread_routine(size_t id)
{
	for (int i = 0; i < 50000; i++) {
		void *res = ma_get(id);
		EXPECT_NE((void*)NULL, res);
		res = ma_get(id);
		EXPECT_NE((void*)NULL, res);
		res = ma_get(id);
		EXPECT_NE((void*)NULL, res);

		int ret = ma_release(id);
		EXPECT_EQ(1, ret);
		ret = ma_release(id);
		EXPECT_EQ(1, ret);
		ret = ma_release(id);
		EXPECT_EQ(1, ret);
	}
}


TEST(AllocFunctionTests, ma_allocator_simple_multithread) {
	int max_threads = 1;
	EXPECT_EQ(1, ma_init(4500, 10000, "some_path"));
	for (int thrs = 1; thrs <= max_threads; thrs++) {
		LOG_INFO("thrs <%d>", thrs);
		size_t id[thrs] ; 
		
		
		thread* threads[thrs];
		for (int i = 0; i < thrs; i++) {
			id[i]= ma_alloc(40);
			EXPECT_LE(1, id[i]);
			threads[i] = new thread(ma_allocator_simple_multithread_routine, id[i]);
		}
		for (int i = 0; i < thrs; i++) {
			threads[i]->join();
			int ret = ma_free(id[i]);
			EXPECT_EQ(1, ret);
		}
	}
	ma_deinit();
}


void test_routine()
{
	int repeat = 10000;
	for (int i = 0; i < repeat; i++) {
		size_t chunk_1 = ma_alloc(128);
		size_t chunk_2 = ma_alloc(256);
		size_t chunk_3 = ma_alloc(64);
		size_t chunk_4 = ma_alloc(64);
		
		ASSERT_NE(0, chunk_1);
		ASSERT_NE(0, chunk_2);
		ASSERT_NE(0, chunk_3);
		ASSERT_NE(0, chunk_4);

		void* data_3 = ma_get(chunk_3);
		void* data_4 = ma_get(chunk_4);

		ASSERT_NE((void*)0, data_3);
		ASSERT_NE((void*)0, data_4);

		sprintf((char*)data_3, "%s", "BAZ\n");
		sprintf((char*)data_4, "%s", "^^");

		
		void* data_1 = ma_get(chunk_1);
		ASSERT_NE((void*)0, data_1);
		memset(data_1, 0, 10);
		sprintf((char*)data_1, "%s", "FOOBAR\n");



		ASSERT_NE(0, ma_release(chunk_3));
		ASSERT_NE(0, ma_release(chunk_4));
		

		void* data_2 = ma_get(chunk_2);
		ASSERT_NE((void*)0, data_2);



		memset(data_2, 0, 16);
		sprintf((char*)data_2, "%s", "0xdeadbeef\n");
	

		ASSERT_EQ(1, ma_release(chunk_1));
		
		ASSERT_NE((void*)0, ma_get(chunk_3));
		ASSERT_NE((void*)0, ma_get(chunk_4));
		ASSERT_NE(0, ma_release(chunk_3));
		ASSERT_NE(0, ma_release(chunk_4));
		
		ASSERT_NE((void*)0, data_1 = ma_get(chunk_1));
		
		ASSERT_NE(0, ma_release(chunk_2));
		
		
		ASSERT_NE((void*)0, ma_get(chunk_3));
		ASSERT_NE((void*)0, ma_get(chunk_4));
		ASSERT_NE(0, ma_release(chunk_3));
		ASSERT_NE(0, ma_release(chunk_4));
		
		ASSERT_EQ(0, strcmp((char*)data_1, "FOOBAR\n"));
		data_2 = ma_get(chunk_2);
		
		ASSERT_NE(0, ma_release(chunk_1));	
		ASSERT_EQ(0, strcmp((char*)data_2, "0xdeadbeef\n"));
		
		
		data_3 = ma_get(chunk_3);

		ASSERT_NE((void*)0, data_3);
		ASSERT_EQ(0, strcmp((char*)data_3, "BAZ\n"));
		
		data_4 = ma_get(chunk_4);

		ASSERT_NE((void*)0, data_4);
		ASSERT_EQ(0, strcmp((char*)data_4, "^^"));
		
		
		
		ASSERT_NE((void*)0, ma_get(chunk_3));
		ASSERT_NE((void*)0, ma_get(chunk_4));
		ASSERT_NE((void*)0, ma_get(chunk_3));
		ASSERT_NE((void*)0, ma_get(chunk_4));
		ASSERT_NE((void*)0, ma_get(chunk_3));
		ASSERT_NE((void*)0, ma_get(chunk_4));

		ASSERT_NE(0, ma_release(chunk_2));
		
		ASSERT_NE(0, ma_release(chunk_4));
		ASSERT_NE(0, ma_release(chunk_4));
		ASSERT_NE(0, ma_release(chunk_4));
		ASSERT_NE(0, ma_release(chunk_3));
		ASSERT_NE(0, ma_release(chunk_4));
		ASSERT_NE(0, ma_release(chunk_3));
		ASSERT_NE(0, ma_release(chunk_3));
		ASSERT_NE(0, ma_release(chunk_3));
	

		ASSERT_NE(0, ma_free(chunk_2));
		ASSERT_NE(0, ma_free(chunk_1));
		ASSERT_NE(0, ma_free(chunk_3));
		ASSERT_NE(0, ma_free(chunk_4));

	}
}

int NUM_THREADS = 1;

TEST(AllocFunctionTests, ma_allocator_big_multithread) {
	ASSERT_EQ(1, ma_init(52800, 250000, "tmp.txt"));
	int attempts = 1;
	for (int threads = 1; threads <= NUM_THREADS; threads++) {
		LOG_INFO("threads: <%d>", threads);
		for(int i = 0; i < attempts; i++) {
			thread* thr[NUM_THREADS];
			for (int i = 0; i < NUM_THREADS; i++)
				thr[i] = new thread(test_routine);
			
			for (int i = 0; i < NUM_THREADS; i++) {
				thr[i]->join();
				delete thr[i];
			}
		}
	}
	
	ma_deinit();
}




#endif //TESTS_CPP_
